package ua.bocha.annotation.validation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ua.bocha.annotation.validation.impl.ValidateException;
import ua.bocha.annotation.validation.impl.ValidateMsg;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * DateEqual: 08.08.12
 *
 * @author bocha
 */
public class ValidateManager {
    private static final Logger LOG = LoggerFactory.getLogger(ValidateManager.class);

    private final Class clazz;
    private final Object validateObject;

    private final TreeMap<String, HashSet<ValidateParameter>> validateParamMap = new TreeMap<String, HashSet<ValidateParameter>>();
    private final HashSet<ValidateMsg> wrongParamMap = new HashSet<ValidateMsg>();

    public ValidateManager(Object validateObject) {
        this.validateObject = validateObject;
        this.clazz = this.validateObject.getClass();
    }

    public void validate() throws Exception {
        try {
            Class<?>[] interfaces = clazz.getInterfaces();
            for (Class<?> iface : interfaces) {
                stepOnInterface(iface);
            }
            verification();
            if (!wrongParamMap.isEmpty()) {
                for (ValidateMsg wrongValue : wrongParamMap) {
                    String parameterName = wrongValue.getParameterName();
                    Method metod = getMethodByParameter("set", parameterName);
                    if (metod != null) {
                        Class parameterClass = wrongValue.getParameterType();
                        if (parameterClass == null) {
                            continue;
                        }
                        metod.invoke(validateObject, new Object[] { null });
                    }
                }
            }
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            throw new Exception(e.getMessage(), e);
        } finally {
            validateParamMap.clear();
            wrongParamMap.clear();
        }

    }


    private void stepOnInterface(Class<?> iface) {
//        LOG.info(String.format("Interface name: %s", iface.getName()));
        Method[] methods = iface.getMethods();
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            if (annotations.length > 0) {
                String methodName = method.getName();
                getMethodAnnotation(methodName, annotations);
            }
        }

    }

    private void getMethodAnnotation(String methodName, Annotation[] annotations) {
        HashSet<ValidateParameter> validateParameterMapList = validateParamMap.get(methodName);
        if (validateParameterMapList == null) {
            validateParameterMapList = new HashSet<ValidateParameter>();
            validateParamMap.put(methodName, validateParameterMapList);
        }

        for (Annotation annotation : annotations) {
            if (annotation instanceof ValidateParameter) {
                ValidateParameter validateParameter = (ValidateParameter) annotation;
                validateParameterMapList.add(validateParameter);
            } else if (annotation instanceof Validators) {
                Validators validators = (Validators) annotation;
                ValidateParameter[] validateParameterList = validators.value();
                for (ValidateParameter validateParameter : validateParameterList) {
                    validateParameterMapList.add(validateParameter);
                }
            }
        }
    }

    private void verification() {
        try {
            for (Map.Entry<String, HashSet<ValidateParameter>> valMap : validateParamMap.entrySet()) {
                HashSet<ValidateParameter> validateParameterList = valMap.getValue();
                for (Iterator<ValidateParameter> iter = validateParameterList.iterator(); iter.hasNext(); ) {
                    ValidateParameter validateParameter = iter.next();
                    if (validateParameter.validateType().length == 0) {
                        continue;
                    }
                    String parameterName = validateParameter.source();
                    Object returnValue = getValueByParamName(parameterName);
                    verifyValue(returnValue, validateParameter);
                }
            }
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
    }

    private void verifyValue(Object value, ValidateParameter validateParameter) {
//        LOG.info(String.format("\tVerify: %s = %s on %s",
//                validateParameter.source(), value, Arrays.asList(validateParameter.validateType())));

        List<ValidateType> validateTypeList = Arrays.asList(validateParameter.validateType());

        if (value == null
                && !Collections.disjoint(Arrays.asList(ValidateType.IS_NULL), validateTypeList)) {
            addValidateMsg("Wrong parameter.", value, validateParameter);
        } else if (value instanceof String
                && !Collections.disjoint(Arrays.asList(
                ValidateType.EMPTY, ValidateType.DEFAULT), validateTypeList)) {
            String validateValue = String.valueOf(value).trim();
            if (validateValue.isEmpty()) {
                addValidateMsg("Wrong parameter.", value, validateParameter);
            }
        } else if (value instanceof Integer
                && !Collections.disjoint(Arrays.asList(
                ValidateType.EMPTY, ValidateType.DEFAULT), validateTypeList)) {
            Integer validateValue = (Integer) value;
            if (validateValue.compareTo(0) == 0) {
                addValidateMsg("Wrong parameter.", value, validateParameter);
            }

        } else if (value instanceof BigDecimal
                && !Collections.disjoint(Arrays.asList(
                ValidateType.EMPTY, ValidateType.DEFAULT), validateTypeList)) {
            BigDecimal validateValue = (BigDecimal) value;
            if (validateValue.compareTo(BigDecimal.ZERO) == 0) {
                addValidateMsg("Wrong parameter.", value, validateParameter);
            }

        } else if (value instanceof Date
                && !Collections.disjoint(Arrays.asList(
                ValidateType.EMPTY, ValidateType.DEFAULT), validateTypeList)) {
            Date validateValue = (Date) value;
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            if ("1900-01-01".equals(sdf.format(validateValue))) {
                addValidateMsg("Wrong parameter.", value, validateParameter);
            }

        }

    }

    private Object getValueByParamName(String parameter) throws Exception {
        Object returnValue = null;
        Method method = getMethodByParameter("get", parameter);
        if (method != null) {
            returnValue = method.invoke(validateObject, null);
        } else {
            LOG.error(String.format("### Exception: Method 'get' for param '%s' not found.", parameter));
//            throw new NullPointerException(String.format("Method 'get' for param '%s' not found.", parameter));
        }
        return returnValue;
    }

    private Method getMethodByParameter(String prefix, String parameter) {
        Method methodReturn = null;
        Method[] methods = clazz.getMethods();
        if (methods.length > 0) {
            for (Method method : methods) {
                String methodName = method.getName();
                String methodSearch = prefix + parameter;
                if (methodName.equalsIgnoreCase(methodSearch)) {
                    methodReturn = method;
                }
            }
        }
        return methodReturn;
    }

    private void addValidateMsg(String message, Object value, ValidateParameter validateParameter) {
        Class valueClass = null;
        if (value != null) {
            valueClass = value.getClass();
        }
        ValidateMsg validateMsg = new ValidateMsg();
        validateMsg.setParameterName(validateParameter.source());
        validateMsg.setParameterType(valueClass);
        validateMsg.setMessage(message);
        wrongParamMap.add(validateMsg);
    }

}
